package com.myphoto.base.aspect;

import cn.hutool.json.JSONUtil;
import com.myphoto.base.annotation.SysLog;
import com.myphoto.base.config.properties.IConfig;
import com.myphoto.base.enumtype.LogTypeEnum;
import com.myphoto.base.enumtype.MsgEnum;
import com.myphoto.base.log.service.LogService;
import com.myphoto.base.mybatis.log.entity.TbLogSysEntity;
import com.myphoto.base.util.R;
import com.myphoto.base.util.Redis;
import com.myphoto.base.util.UserFun;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * 系统日志，切面处理类
 */
@Aspect
@Component
public class SysLogAspect extends AbstractController {
    @Resource
    private LogService logService;
    @Autowired
    private Redis redis;
    @Autowired
    IConfig config;

    private Logger log = LoggerFactory.getLogger("root");

    @Pointcut("@annotation(com.myphoto.base.annotation.SysLog)")
    public void logPointCut() {

    }

    //统一异常拦截处理
    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        try{
            //保存操作日志
            saveSysLog(point,null);
            //执行方法
            return point.proceed();
        }catch (Exception e){
            //在控制台和日志文件输出异常信息
            log.error(MsgEnum.SYSTEM_ERROR.getMsg(),e);
            //返回错误提示
            return R.error(MsgEnum.SYSTEM_ERROR);
        }
    }

    /**
     * 异常处理（记录异常日志）
     */
    @AfterThrowing(value = "logPointCut()", throwing = "e")
    public void aroundThrowing(JoinPoint joinPoint, Exception e){
        saveSysLog(joinPoint,e);
    }

    private void saveSysLog(JoinPoint joinPoint,Exception e) {
        boolean log_record_enable = Boolean.parseBoolean(config.getConfig().getLog_record_enable());
        if(!log_record_enable){
            return ;
        }
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        //获取request
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = servletRequestAttributes.getRequest();
        HttpServletResponse response = servletRequestAttributes.getResponse();

        TbLogSysEntity sysLogEntity = new TbLogSysEntity();
        SysLog syslog = method.getAnnotation(SysLog.class);
        //@SysLog 注解上的属性
        sysLogEntity.setContent(syslog.value());
        sysLogEntity.setModule(syslog.model().name());
        if(e!=null){
            //有异常发生时，日志级别固定为ERROR
            sysLogEntity.setLogType(LogTypeEnum.ERROR.name());
        }else{
            sysLogEntity.setLogType(syslog.logType().name());
        }

        //请求的方法名
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = signature.getName();
        sysLogEntity.setMethodUrl(className + "." + methodName + "()");
        //请求的参数
        Object[] args = joinPoint.getArgs();
        final String[] parameterNames = signature.getParameterNames();
        final Class[] parameterTypes = signature.getParameterTypes();
        String params;
        try {
            String showParamValue = null;
            List<Object> paras = new ArrayList<>();
            for(int i=0; i<parameterNames.length; i++){
                if("request".equals(parameterNames[i]) || "response".equals(parameterNames[i])){
                    continue;
                }
                String thisPackageName = this.getClass().getPackage().getName();
                int baseIndex = thisPackageName.indexOf("base");
                String packageStart = thisPackageName.substring(0,baseIndex);
                String packageName = parameterTypes[i].getPackage().getName();
                //自定义实体参数
                if(packageName.startsWith(packageStart)){
                    String param = JSONUtil.toJsonStr(args[i]);
                    paras.add(param);
                    showParamValue = showParamValue(args[i],syslog.showParam());
                }
                //Java基本类型参数
                else{
                    String paramValue = parameterNames[i]+":"+args[i];
                    paras.add(paramValue);
                    if(parameterNames[i].equals(syslog.showParam())){
                        showParamValue = paramValue;
                    }
                }
            }
            params = JSONUtil.toJsonStr(paras);
            if(syslog.showParam()!=null && syslog.showParam().trim().length()>0){
                if(showParamValue!=null){
                    MDC.put("showParam",showParamValue);//注入日志属性值 showParam
                }else{
                    MDC.put("showParam","方法参数【"+syslog.showParam()+"】的值为空！");//注入日志属性值 showParam
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
            params = "日志转换请求参数Json转换异常!";
        }
        sysLogEntity.setMethodParams(params);
        //设置IP地址
        sysLogEntity.setIp(UserFun.getIpAddr(request));
        sysLogEntity.setLogTime(new Date());
        sysLogEntity.setId(UserFun.getUUID32());

        if(e==null){
            if("DEBUG".equals(syslog.logType().name())){
                log.debug(syslog.value());
            }else if("INFO".equals(syslog.logType().name())){
                log.info(syslog.value());
            }else if("WARN".equals(syslog.logType().name())){
                log.warn(syslog.value());
            }else if("ERROR".equals(syslog.logType().name())){
                log.error(syslog.value(),e);
            }
        }
        MDC.remove("user");
        MDC.remove("showParam");

        if(e!=null){
            //根据异常对象，取得异常堆栈信息（取前面200行）
            String stackTrace = UserFun.getStackTraceForHtml(e, 200);
            sysLogEntity.setStackTrace(stackTrace);
        }

        //保存系统日志到数据库
        if (syslog.saveToDatabase() || e != null) {
            logService.saveSysLog(sysLogEntity);
        }

    }

    private String showParamValue(Object arg0,String showParam){
        String paramValue = null;
        try{
            if(showParam==null || showParam.trim().length()==0){
                return paramValue;
            }
            String thisPackageName = this.getClass().getPackage().getName();
            int baseIndex = thisPackageName.indexOf("base");
            String packageStart = thisPackageName.substring(0,baseIndex);
            int newPointIndex = showParam.indexOf(".");
            if(newPointIndex>0){
                //利用Java反射机制，获取实体字段的数据
                int oldPointIndex = -1;
                Object objTmp = arg0;
                while (newPointIndex>0){
                    String filedName = showParam.substring(oldPointIndex+1,newPointIndex);
                    Object obj = UserFun.doGetMethod(objTmp,filedName);
                    if(obj!=null){
                        String objPackageName = obj.getClass().getPackage().getName();
                        if(objPackageName.startsWith(packageStart)){
                            oldPointIndex = newPointIndex;
                            objTmp = obj;
                            newPointIndex = showParam.indexOf(".",newPointIndex+1);
                            if(newPointIndex<0){
                                filedName = showParam.substring(oldPointIndex+1,showParam.length());
                                Object objLast = UserFun.doGetMethod(objTmp,filedName);
                                if(objLast!=null){
                                    paramValue = showParam+":"+objLast.toString();
                                }else{
                                    break;
                                }
                            }
                        }else{
                            break;
                        }
                    }else{
                        break;
                    }
                }
            }else{
                Object paramVal = UserFun.doGetMethod(arg0,showParam);
                if(paramVal!=null){
                    paramValue = showParam+":"+paramVal.toString();
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return paramValue;
    }

    @Override
    protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
        return null;
    }

}
